/*
 This program is submitted as part of an assignment in the
 J2E
 Fall Semester 2010-2011 
 School of Computer Science
 Interdisciplinary Center, Hertzelia, Israel 

 http://www.idc.ac.il 

 Exercise No.         : 3
 File Name            : GameView.java
 Names (First Last)   : Itay Sorani, Nadav Ziv, Yonathan Zarsky
 Student No.          : 066530254, 039937883, 039692918
 */

package com.example.View;
 
import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;
import android.widget.Toast;

import com.example.View.Renderers.BuildingRenderer;
import com.example.View.Renderers.FigureRenderer;
import com.example.View.Renderers.GeneralRenderer;
import com.example.View.Renderers.TileRenderer;
import com.example.View.Renderers.TowerRenderer;
import com.example.View.Renderers.UnitRenderer;
import com.example.models.Building;
import com.example.models.Tower;
import com.example.util.Figure;
import com.example.util.FigureType;
import com.example.util.GameModel;
import com.example.util.TileType;
import com.example.util.UserRequestListener;
import com.example.util.Vec2D;

/**
 * The view of the game sends events of move/create model to the client controller, to send to server. 
 * The GameView class. responsible for model drawing, displaying messages during runtime and listening to
 * key events.
 * @param models a reference to the game models, in order to enable model drawing.
 * @param gameLogic reference to the game logic. in order to enable key handling.
 * in next exercises events will be used to prevent saving the GameView reference in this class. 
 * @param canvasSet determines whether the canvas size was initialized and updated also in game logic.
 * @param toast enables messages display during runtime.
 */
public class GameView extends View implements OnGestureListener, OnDoubleTapListener{
	
	// the view scale when zoom out
	private static final float X_SCALE_WHEN_ZOOM_OUT = 0.4f;
	private static final float Y_SCALE_WHEN_ZOOM_OUT = 0.4f;
	 
	// the map width. measured in cells - view will render each cell as 40 pixels.
	private static final int MAP_WIDTH = 38;
	// the map height. measured in cells - view will render each cell as 40 pixels.
	private static final int MAP_HEIGHT = 18;
	
	// the game figures to draw
	private GameModel gameModel;
	// maps a figure type to the correct figure renderer
	private Map<FigureType, FigureRenderer> rendererByFigureType;
	// determines whether the canvas was set
	private boolean canvasSet = false;
	// a toast message
	private Toast toastMsg;
	// the game view user request listener, handles every user request and notifies the controller.
	private UserRequestListener userRequestListener;
	private GestureDetector gestureDetector;
	
	// pixels per map cell
    private static final int PIXELS_PER_CELL = 40;
    
    private final static float canvasMovementVelocity = 20.f;
    
	// pixels per cell
	private int pixelsPerCell = PIXELS_PER_CELL; ;
	
	// the canvas position
	private float x,y;
	// the canvas scale
	private float xScale,yScale;
	// is the game in zoom out
	private boolean zoomOut;
	// canvas width and height.
	private float canvasHeight;
	private float canvasWidth;
	// game view projection
	private Projection projection; 
	// the background of the game
	private TileType[][] background;
	// should draw grid? (press 6 to draw)
	private boolean drawGrid;
	private Vec2D canvasMovementDestination;
	private boolean isMovingCanvas;
	
	private TileRenderer renderer;
	
	/**
     * Constructs the game view and sets its variables.
     */
	public GameView (Context context, AttributeSet attrs){
		super(context, attrs);
		this.xScale = 1f;
		this.yScale = 1f;
		this.zoomOut = false;
		renderer = new TileRenderer(getResources());
		isMovingCanvas = false;
		this.drawGrid = false;
		gestureDetector = new GestureDetector(this);
		gestureDetector.setOnDoubleTapListener(this);
		setFocusable(true);
		setupProjection();
		init();
	}
	
	/**
	 *  Performed on each draw.
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		if (this.canvasSet == false) {
			this.canvasSet = true;
			setCanvasWidth(super.getWidth());
			setCanvasHeight(super.getHeight());
			canvasSet = true;
		}
		
		if (isMovingCanvas) {
			Vec2D currentPosition = new Vec2D(projection.getX(),projection.getY());
			if (currentPosition.getDist(this.canvasMovementDestination) > canvasMovementVelocity) {
				Vec2D direction = new Vec2D(canvasMovementDestination.getX() - currentPosition.getX(),canvasMovementDestination.getY() - currentPosition.getY());
				direction.normalize();
				projection.setX(projection.getX() + direction.getX() * canvasMovementVelocity);
				projection.setY(projection.getY() + direction.getY() * canvasMovementVelocity);
			} else {
				this.isMovingCanvas = false;
			}
		}
		Paint paint = new Paint();
		paint.setColor(Color.WHITE);
		canvas.scale(xScale, yScale,0,0);
        
		drawBackground(canvas);
		drawFigures(canvas); 
		if (this.drawGrid == true) {
			for (int i = 0; (i <= MAP_HEIGHT) || (i <= MAP_WIDTH); i++) {
				canvas.drawLine(0 - projection.getX(), i * pixelsPerCell - projection.getY() , (MAP_WIDTH * pixelsPerCell)  - projection.getX(), i * pixelsPerCell - projection.getY(), paint);
				canvas.drawLine(i * pixelsPerCell - projection.getX(), 0 - projection.getY() , i * pixelsPerCell - projection.getX(), (MAP_HEIGHT * pixelsPerCell) - projection.getY(), paint);
			}
		}
    }
	
	/**
	 *  Does the actual initialization of the view
	 */
	private void init() {
		Resources resources = getResources();
        rendererByFigureType = new HashMap<FigureType, FigureRenderer>();
        rendererByFigureType.put(FigureType.unit, new UnitRenderer(resources));
        rendererByFigureType.put(FigureType.tower, new TowerRenderer(resources));
        rendererByFigureType.put(FigureType.building, new BuildingRenderer(resources));
        rendererByFigureType.put(FigureType.general, new GeneralRenderer(resources));
	}
	
	/**
	 *  Draws the different game figures
	 */
	private void drawFigures(Canvas canvas) {
		if (gameModel != null) {
			int count = gameModel.getNumOfModels();
			for (int i=0; i<count; i++,count = gameModel.getNumOfModels()) {
				Figure figure = gameModel.getModel(i);
				if (figure != null) {
					FigureType type = figure.getFigureType();
					FigureRenderer renderer = rendererByFigureType.get(type);
					if (renderer != null) {
						renderer.render(canvas, projection, figure);
					}
				}
			}
		}
    }

	/**
	 *  Draws the different game figures
	 */
	private void drawBackground(Canvas canvas) {
		for (int i= 0; i <= ((canvasWidth / pixelsPerCell) / xScale); i++) {
			for (int j=0; j <= ((canvasHeight / pixelsPerCell) / yScale) ; j++) {
				float tempX = i + (projection.getX() / pixelsPerCell);
				float tempY = j + (projection.getY() / pixelsPerCell);
				if ((((tempX < 0) && (tempX > -2) && (tempY > -2) && (tempY <= MAP_HEIGHT + 1)) ||
					((tempX >= MAP_WIDTH) && (tempX < MAP_WIDTH + 1) && (tempY > -2) && (tempY <= MAP_HEIGHT + 1) )) ||
					(((tempY < 0) && (tempY > -2) && (tempX > -2) && (tempX <= MAP_WIDTH + 1)) ||
					((tempY >= MAP_HEIGHT) && (tempY < MAP_HEIGHT + 1) && (tempX > -2) && (tempX <= MAP_WIDTH + 1)))) {
						renderer.render(canvas, projection, TileType.rocks, (int)(i + projection.getX() / pixelsPerCell),(int)(j + projection.getY() / pixelsPerCell));
				
				} else if ((tempX < 0) || (tempX >= MAP_WIDTH) ||
					       (tempY < 0) || (tempY >= MAP_HEIGHT)) {
					renderer.render(canvas, projection, TileType.sea, (int)(tempX),(int)(tempY));
				} else {
					TileType tileType = background[(int)(tempX)][(int)(tempY)];
					if (tileType != null) {
						if (renderer != null) {
							renderer.render(canvas, projection, tileType, (int)(tempX), (int)(tempY));
						}
					}
				}
			}
		}
    }

	
	/**
	 * handles any key event during runtime.
	 */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent msg) {
    	switch (keyCode) {
    	case KeyEvent.KEYCODE_DPAD_LEFT:
    		x -= 20 / (xScale * xScale);
    		break;
    	case KeyEvent.KEYCODE_DPAD_RIGHT:
    		x += 20 / (xScale * xScale);
    		break;
    	case KeyEvent.KEYCODE_DPAD_UP:
    		y -= 20 / (yScale * yScale);
    		break;
    	case KeyEvent.KEYCODE_DPAD_DOWN:
    		y += 20 / (yScale * yScale);
    		break;
    	case KeyEvent.KEYCODE_0:
    		Log.v("Msg:", " Scaling");
    		this.xScale += 0.05;
    		this.yScale += 0.05;
    		break;
    	case KeyEvent.KEYCODE_9:
    		Log.v("Msg:", " Scaling");
    		this.xScale -= 0.05;
    		this.yScale -= 0.05;
    		break;
    	case KeyEvent.KEYCODE_5:
    		Log.v("Msg:", " exit");
    		userRequestListener.onGameExit();
    		break;
    	case KeyEvent.KEYCODE_6:
    		Log.v("Msg:", "draw grid");
    		if (this.drawGrid == false) { 
    			this.drawGrid = true;
    		} else {
    			this.drawGrid = false;
    		}
    		
    	default:
    		return false;
    	}
    	postInvalidate();
    	return true;
    }	
    
    /**
	 * handles any key event during runtime.
	 */
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent msg) {
    	
    	return super.onKeyDown(keyCode, msg);
    }
    
    /**
	 *  Displays a message to the canvas.
	 */
    public void displayMesssage(String msg, Vec2D position) {
    	toastMsg = Toast.makeText(this.getContext(), msg, Toast.LENGTH_LONG);
    	toastMsg.setGravity(Gravity.BOTTOM, (int)(position.getX()), (int)(position.getY()));
    	toastMsg.show();
    }
       
    /**
     * Registers a UserRequestListener for listening for user requests to control the game
     */
    public void setUserRequestListener(UserRequestListener listener) {
    	this.userRequestListener = listener;
    }

    /**
	 * handles any touch event during runtime.
	 */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    	return gestureDetector.onTouchEvent(event);
    }
    
    /**
	 * handles on down event during runtime
	 */
	@Override
	public boolean onDown(MotionEvent e) {
		return true;
	}

	/**
	 * handles on fling event during runtime
	 */
	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
		return false;
	}

	/**
	 * handles on long press event during runtime
	 */
	@Override
	public void onLongPress(MotionEvent e) {
	}

	/**
	 * handles scroll event (scroll view around the map).
	 */
	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
			float distanceY) {
		this.projection.setX(projection.getX() + distanceX);
		this.projection.setY(projection.getY() + distanceY);
		return false;
	}
	
	/**
	 * handles onShowPress event
	 */
	@Override
	public void onShowPress(MotionEvent e) {
	}

	/**
	 * handles onSingleTapUp event
	 */
	@Override
	public boolean onSingleTapUp(MotionEvent e) {
		return false;
	}

	/**
	 * handles onDoubleTap event: scale in and out.
	 */
	@Override
	public boolean onDoubleTap(MotionEvent e) {
//		moveCanvasToDestination(new Vec2D(1000,0));
	/*
		if (zoomOut == false) {
			zoomOut = true;
			this.xScale = X_SCALE_WHEN_ZOOM_OUT;
			this.yScale = Y_SCALE_WHEN_ZOOM_OUT;
			//projection.setX(projection.getX() * xScale);
			//projection.setY(projection.getY() * yScale);
			//projection.setPixelsPerCell((int)(projection.getPixelsPerCell() * xScale));
		} else {
			zoomOut = false;
			//projection.setX(projection.getX() / xScale);
			//projection.setY(projection.getY() / yScale);
			//projection.setPixelsPerCell((int)(projection.getPixelsPerCell() / xScale));
			this.xScale = 1;
			this.yScale = 1;
		} 
		*/
		return false;
	}

	/**
	 * handles onDoubleTapEvent event
	 */
	@Override
	public boolean onDoubleTapEvent(MotionEvent e) {
		return false;
	}

	/**
	 * handles onSingleTapConfirmed event (notify controller of a single tap and its location, activates the model creation menu)
	 */
	@Override
	public boolean onSingleTapConfirmed(MotionEvent e) {
		RectF rectangle = null;
		for (int i = 0; i < this.gameModel.getNumOfModels(); i++) {
			float x, y, right, bottom;
			Figure figure = gameModel.getModel(i);
			if (figure instanceof Building) {
				BuildingRenderer renderer = (BuildingRenderer)rendererByFigureType.get(figure.getFigureType());
				x = figure.getX() * pixelsPerCell;
				y = figure.getY() * pixelsPerCell;
				right = x + renderer.getBitmap((Building)figure).getWidth();
				bottom = y + renderer.getBitmap((Building)figure).getHeight();
				rectangle = new RectF(x,y, right, bottom);
			} else if (figure instanceof Tower) {
				TowerRenderer renderer = (TowerRenderer)rendererByFigureType.get(figure.getFigureType());
				x = figure.getX() * pixelsPerCell;
				y = figure.getY() * pixelsPerCell;
				right = x + renderer.getBitmap((Tower)figure).getWidth();
				bottom = y + renderer.getBitmap((Tower)figure).getHeight();
				rectangle = new RectF(x,y, right, bottom);
			}
			
			if (rectangle != null && rectangle.contains(e.getX() + projection.getX(),e.getY() + projection.getY())) {
				userRequestListener.onNewModelCreationRequest((int)(e.getX() + projection.getX()) / pixelsPerCell, (int)(e.getY() + projection.getY()) / pixelsPerCell);
				break;
			}
		}
		TileType cell = background[(int)((projection.getX() + e.getX()) / pixelsPerCell)][(int)((projection.getY() + e.getY()) / pixelsPerCell)];
		if (cell != null && cell == TileType.mulch) {
			userRequestListener.onNewModelCreationRequest((int)(e.getX() + projection.getX()) / pixelsPerCell, (int)(e.getY() + projection.getY()) / pixelsPerCell);
		}
		return false;
	}
	
	/**
	 * setup this view projection
	 */
	public void setupProjection() {
		this.projection = new Projection(pixelsPerCell);
		projection.setX(0);
		projection.setY(0);
		projection.setxScale(1);
		projection.setyScale(1);
	}
	
	/**
	 * sets the canvas height
	 */
	public void setCanvasHeight(float canvasHeight) {
		this.canvasHeight = canvasHeight;
	}
	
	/**
	 * returns the canvas height
	 */
	public float getCanvasHeight() {
		return canvasHeight;
	}

	/**
	 * sets the canvas width
	 */
	public void setCanvasWidth(float canvasWidth) {
		this.canvasWidth = canvasWidth;
	}

	/**
	 * returns the canvas height
	 */
	public float getCanvasWidth() {
		return canvasWidth;
	}

	/**
	 * sets the GameModel
	 */
	public void setGameModel(GameModel gameModel) {
		this.gameModel = gameModel;
	}
	 
	/**
	 * returns the GameModel
	 */
	public GameModel getGameModel() {
		return gameModel;
	}

	/**
	 * sets the game view
	 */
	public void setView(int pixelsPerCell) {
		this.pixelsPerCell = pixelsPerCell;
		setupProjection();
	}
	
	/**
	 * returns pixelsPerCell
	 */
	public int getPixelsPerCell() {
		return pixelsPerCell;
	}
	
	public void moveCanvasToDestination (Vec2D destination) {
		this.canvasMovementDestination = new Vec2D (destination.getX() * pixelsPerCell,destination.getY() * pixelsPerCell);
		this.isMovingCanvas = true;
	}
	
	public void setBackgroundTile(TileType cellType, int i, int j) {
		this.background[i][j] = cellType;
	}
	
	public void initBackground(int width, int height){
		this.background = new TileType[width][height];
	}
	
}